//
//  YTXGeometry.h
//  ChartRefactor
//
//  Created by CaoJun on 15/3/2.
//  Copyright (c) 2015年 CaoJun. All rights reserved.
//

#ifndef __ChartRefactor__YTXGeometry__
#define __ChartRefactor__YTXGeometry__

#include "math.h"
#include "float.h"

namespace ytx {

class YTXSize;

class YTXPoint
{
public:
    float x;
    float y;

public:
    YTXPoint();
    YTXPoint(float x, float y);

    YTXPoint(const YTXPoint& other);

    YTXPoint(const YTXSize& size);

    YTXPoint& operator= (const YTXPoint& other);

    YTXPoint& operator= (const YTXSize& size);

    YTXPoint operator+(const YTXPoint& right) const;

    YTXPoint operator-(const YTXPoint& right) const;

    YTXPoint operator-() const;

    YTXPoint operator*(float a) const;

    YTXPoint operator/(float a) const;

    void setPoint(float x, float y);
    bool equals(const YTXPoint& target) const;

    /** @returns if points have fuzzy equality which means equal with some degree of variance.
     */
    bool fuzzyEquals(const YTXPoint& target, float variance) const;

    /** Calculates distance between point an origin
     * @return float
     */
    inline float getLength() const {
        return sqrtf(x*x + y*y);
    };

    /** Calculates the square length of a YTXPoint (not calling sqrt() )
     * @return float
     */
    inline float getLengthSq() const {
        return dot(*this); //x*x + y*y;
    };

    /** Calculates the square distance between two points (not calling sqrt() )
     @return float
     */
    inline float getDistanceSq(const YTXPoint& other) const {
        return (*this - other).getLengthSq();
    };

    /** Calculates the distance between two points
     @return float
     */
    inline float getDistance(const YTXPoint& other) const {
        return (*this - other).getLength();
    };

    /** @returns the angle in radians between this vector and the x axis
     */
    inline float getAngle() const {
        return atan2f(y, x);
    };

    /** @returns the angle in radians between two vector directions
     */
    float getAngle(const YTXPoint& other) const;

    /** Calculates dot product of two points.
     @return float
     */
    inline float dot(const YTXPoint& other) const {
        return x*other.x + y*other.y;
    };

    /** Calculates cross product of two points.
     @return float
     */
    inline float cross(const YTXPoint& other) const {
        return x*other.y - y*other.x;
    };

    /** Calculates perpendicular of v, rotated 90 degrees counter-clockwise -- cross(v, perp(v)) >= 0
     @return YTXPoint
     */
    inline YTXPoint getPerp() const {
        return YTXPoint(-y, x);
    };

    /** Calculates perpendicular of v, rotated 90 degrees clockwise -- cross(v, rperp(v)) <= 0
     @return YTXPoint
     */
    inline YTXPoint getRPerp() const {
        return YTXPoint(y, -x);
    };

    /** Calculates the projection of this over other.
     @return YTXPoint
     */
    inline YTXPoint project(const YTXPoint& other) const {
        return other * (dot(other)/other.dot(other));
    };

    /** Complex multiplication of two points ("rotates" two points).
     @return YTXPoint vector with an angle of this.getAngle() + other.getAngle(),
     and a length of this.getLength() * other.getLength().
     */
    inline YTXPoint rotate(const YTXPoint& other) const {
        return YTXPoint(x*other.x - y*other.y, x*other.y + y*other.x);
    };

    /** Unrotates two points.
     @return YTXPoint vector with an angle of this.getAngle() - other.getAngle(),
     and a length of this.getLength() * other.getLength().
     */
    inline YTXPoint unrotate(const YTXPoint& other) const {
        return YTXPoint(x*other.x + y*other.y, y*other.x - x*other.y);
    };

    /** Returns point multiplied to a length of 1.
     * If the point is 0, it returns (1, 0)
     @return YTXPoint
     */
    inline YTXPoint normalize() const {
        float length = getLength();
        if(length == 0.) return YTXPoint(1.f, 0);
        return *this / getLength();
    };

    /** Linear Interpolation between two points a and b
     @returns
     alpha == 0 ? a
     alpha == 1 ? b
     otherwise a value between a..b
     */
    inline YTXPoint lerp(const YTXPoint& other, float alpha) const {
        return *this * (1.f - alpha) + other * alpha;
    };

    /** Rotates a point counter clockwise by the angle around a pivot
     @param pivot is the pivot, naturally
     @param angle is the angle of rotation ccw in radians
     @returns the rotated point
     */
    YTXPoint rotateByAngle(const YTXPoint& pivot, float angle) const;

    static inline YTXPoint forAngle(const float a)
    {
        return YTXPoint(cosf(a), sinf(a));
    }
};

class YTXSize
{
public:
    float width;
    float height;

public:
    YTXSize();
    YTXSize(float width, float height);

    YTXSize(const YTXSize& other);

    YTXSize(const YTXPoint& point);

    YTXSize& operator= (const YTXSize& other);

    YTXSize& operator= (const YTXPoint& point);

    YTXSize operator+(const YTXSize& right) const;

    YTXSize operator-(const YTXSize& right) const;

    YTXSize operator*(float a) const;

    YTXSize operator/(float a) const;

    void setSize(float width, float height);

    bool equals(const YTXSize& target) const;
};

class YTXRect
{
public:
    YTXPoint origin;
    YTXSize  size;

public:
    YTXRect();
    YTXRect(float x, float y, float width, float height);

    YTXRect(const YTXRect& other);

    YTXRect& operator= (const YTXRect& other);
    void setRect(float x, float y, float width, float height);
    float getMinX() const; /// return the leftmost x-value of current rect
    float getMidX() const; /// return the midpoint x-value of current rect
    float getMaxX() const; /// return the rightmost x-value of current rect
    float getMinY() const; /// return the bottommost y-value of current rect
    float getMidY() const; /// return the midpoint y-value of current rect
    float getMaxY() const; /// return the topmost y-value of current rect
    bool equals(const YTXRect& rect) const;
    bool containsPoint(const YTXPoint& point) const;
    bool intersectsRect(const YTXRect& rect) const;
};


#define YTXPointMake(x, y) YTXPoint((float)(x), (float)(y))
#define YTXSizeMake(width, height) YTXSize((float)(width), (float)(height))
#define YTXRectMake(x, y, width, height) YTXRect((float)(x), (float)(y), (float)(width), (float)(height))

#define ytxp(x, y) YTXPoint((float)(x), (float)(y))

const YTXPoint YTXPointZero = YTXPointMake(0,0);

/* The "zero" size -- equivalent to YTXSizeMake(0, 0). */
const YTXSize YTXSizeZero = YTXSizeMake(0,0);

/* The "zero" rectangle -- equivalent to YTXRectMake(0, 0, 0, 0). */
const YTXRect YTXRectZero = YTXRectMake(0,0,0,0);

};

#endif /* defined(__ChartRefactor__YTXGeometry__) */
